/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice,
     this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright 
     notice, this list of conditions and the following disclaimer in 
     the documentation and/or other materials provided with the distribution.

  3. The names of the authors may not be used to endorse or promote products
     derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jcraft.jsch;

import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

/**
 * 2025.7.10: 密钥交换抽象类
 */
@Slf4j
@ToString
public abstract class KeyExchange {

    /**
     * 密钥交换算法类，序号为0
     */
    static final int PROPOSAL_KEX_ALGS = 0;
    /**
     * 2025.7.16: 服务器的主机key
     */
    static final int PROPOSAL_SERVER_HOST_KEY_ALGS = 1;
    /**
     * 2025.7.15: 协商出来的加密算法(客户端到服务器端)
     */
    static final int PROPOSAL_ENC_ALGS_CTOS = 2;
    /**
     * 2025.7.15: 协商出来的加密算法(服务器端到客户端)
     */
    static final int PROPOSAL_ENC_ALGS_STOC = 3;
    /**
     * 2025.7.15: 协商出来的消息认证算法(客户端到服务器端)
     */
    static final int PROPOSAL_MAC_ALGS_CTOS = 4;
    /**
     * 2025.7.15: 协商出来的消息认证算法(服务器端到客户端)
     */
    static final int PROPOSAL_MAC_ALGS_STOC = 5;
    /**
     * 2025.7.15: 协商出来的压缩算法(客户端到服务器端)
     */
    static final int PROPOSAL_COMP_ALGS_CTOS = 6;
    /**
     * 2025.7.15: 协商出来的压缩算法(服务器端到客户端)
     */
    static final int PROPOSAL_COMP_ALGS_STOC = 7;
    /**
     * 2025.7.16: 语言(客户端到服务器端)
     */
    static final int PROPOSAL_LANG_CTOS = 8;
    /**
     * 2025.7.16: 语言(服务器端到客户端)
     */
    static final int PROPOSAL_LANG_STOC = 9;
    /**
     * 最大协商次数为10次
     */
    static final int PROPOSAL_MAX = 10;

    //static String kex_algs="diffie-hellman-group-exchange-sha1"+
    //                       ",diffie-hellman-group1-sha1";

    //static String kex="diffie-hellman-group-exchange-sha1";
    /**
     * 2025.7.12: 密钥交换算法
     */
    static String kex = "diffie-hellman-group1-sha1";
    /**
     * 2025.7.12: 公钥算法
     */
    static String server_host_key = "ssh-rsa,ssh-dss";
    /**
     * 2025.7.12: 对称加密算法
     */
    static String enc_c2s = "blowfish-cbc";
    static String enc_s2c = "blowfish-cbc";
    /**
     * 2025.7.12: 消息验证完整性
     */
    static String mac_c2s = "hmac-md5";     // hmac-md5,hmac-sha1,hmac-ripemd160,
    // hmac-sha1-96,hmac-md5-96
    static String mac_s2c = "hmac-md5";
    //static String comp_c2s="none";        // zlib
//static String comp_s2c="none";
    static String lang_c2s = "";
    static String lang_s2c = "";

    public static final int STATE_END = 0;

    protected Session session = null;
    protected HASH sha = null;
    /**
     * 2025.7.17: 共同DH计算结果--共享密钥
     */
    protected byte[] K = null;
    protected byte[] H = null;
    /**
     * 2025.7.17: 主机密钥(dh gex reply(33)过程服务器发送host key(RSA公钥))
     */
    protected byte[] K_S = null;

    /**
     * 对密钥交换类进行初始化
     * <p>
     * 会向服务器端发送一个报文，类型为：SSH_MSG_KEX_ECDH_INIT 或 SSH_MSG_KEX_DH_GEX_REQUEST
     *
     * @param session
     * @param serverVersion     服务器端版本
     * @param clientVersion     客户端版本
     * @param serverSupportList 服务器端支持的加密算法列表
     * @param clientSupportList 客户端支持的加密算法列表
     * @throws Exception
     */
    public abstract void init(Session session,
                              byte[] serverVersion, byte[] clientVersion, byte[] serverSupportList, byte[] clientSupportList) throws Exception;

    public abstract boolean next(Buffer buf) throws Exception;

    public abstract int getState();

    protected final int RSA = 0;
    protected final int DSS = 1;
    protected final int ECDSA = 2;
    private int type = 0;
    private String key_alg_name = "";

    public String getKeyType() {
        if (type == DSS) return "DSA";
        if (type == RSA) return "RSA";
        return "ECDSA";
    }

    public String getKeyAlgorithName() {
        return key_alg_name;
    }

    /**
     * 密钥算法协商或者叫密钥算法匹配
     *
     * @param byteServerSupportAlgorithm 服务器端支持的加密算法列表
     * @param byteClientSupportAlgorithm 服务器端支持的加密算法列表
     * @return
     */
    protected static String[] guess(byte[] byteServerSupportAlgorithm, byte[] byteClientSupportAlgorithm) {
//        log.info("[guess] I_S:{}, I_C:{}", Util.byte2str(I_S), Util.byte2str(I_C));
        log.info("[加密算法协商] guess开始，也就是算法协商开始");
        String[] guess = new String[PROPOSAL_MAX];
        Buffer serverProcotolByte = new Buffer(byteServerSupportAlgorithm);
        serverProcotolByte.setOffSet(17);
        Buffer clientProtocolByte = new Buffer(byteClientSupportAlgorithm);
        clientProtocolByte.setOffSet(17);

//        if (JSch.getLogger().isEnabled(Logger.INFO)) {
        for (int i = 0; i < PROPOSAL_MAX; i++) {
            log.info("[服务器端支持的加密算法] server: {}, i:{}", Util.byte2str(serverProcotolByte.getString()), i);
        }

        for (int i = 0; i < PROPOSAL_MAX; i++) {
            log.info("[客户端支持的加密算法] client: {}， i:{}", Util.byte2str(clientProtocolByte.getString()), i);
        }
        serverProcotolByte.setOffSet(17);
        clientProtocolByte.setOffSet(17);
//        }

        for (int i = 0; i < PROPOSAL_MAX; i++) {
            byte[] serverProtocol = serverProcotolByte.getString();  // server proposal
            byte[] clientProtocol = clientProtocolByte.getString();  // client proposal
            int j = 0;
            int k = 0;

            loop:
            while (j < clientProtocol.length) {
                while (j < clientProtocol.length && clientProtocol[j] != ',') j++;
                if (k == j) return null;
                String algorithm = Util.byte2str(clientProtocol, k, j - k);
                int l = 0;
                int m = 0;
                while (l < serverProtocol.length) {
                    while (l < serverProtocol.length && serverProtocol[l] != ',') l++;
                    if (m == l) return null;
                    if (algorithm.equals(Util.byte2str(serverProtocol, m, l - m))) {
                        guess[i] = algorithm;
                        log.info("[算法协商结果] 第{}种算法协商, 协商出来的算法algorithm:{}, 协商轮次:{}", i, guess[i], j);
                        break loop;
                    }
                    l++;
                    m = l;
                }
                j++;
                k = j;
            }
            if (j == 0) {
                guess[i] = "";
            } else if (guess[i] == null) {
                return null;
            }
        }

        log.info("[算法协商类] 总的协商结果 server->client" +
                " " + guess[PROPOSAL_ENC_ALGS_STOC] +
                " " + guess[PROPOSAL_MAC_ALGS_STOC] +
                " " + guess[PROPOSAL_COMP_ALGS_STOC]);
        log.info("[算法协商类] 总的协商结果 client->server" +
                " " + guess[PROPOSAL_ENC_ALGS_CTOS] +
                " " + guess[PROPOSAL_MAC_ALGS_CTOS] +
                " " + guess[PROPOSAL_COMP_ALGS_CTOS]);
//        log.info("guess:{}", guess);
        return guess;
    }

    public String getFingerPrint() {
        HASH hash = null;
        try {
            Class c = Class.forName(session.getConfig("md5"));
            hash = (HASH) (c.newInstance());
        } catch (Exception e) {
            System.err.println("getFingerPrint: " + e);
        }
        return Util.getFingerPrint(hash, getHostKey());
    }

    byte[] getK() {
        return K;
    }

    byte[] getH() {
        return H;
    }

    HASH getHash() {
        return sha;
    }

    byte[] getHostKey() {
        return K_S;
    }

    /*
     * It seems JCE included in Oracle's Java7u6(and later) has suddenly changed
     * its behavior.  The secrete generated by KeyAgreement#generateSecret()
     * may start with 0, even if it is a positive value.
     */
    protected byte[] normalize(byte[] secret) {
        if (secret.length > 1 &&
                secret[0] == 0 && (secret[1] & 0x80) == 0) {
            byte[] tmp = new byte[secret.length - 1];
            System.arraycopy(secret, 1, tmp, 0, tmp.length);
            return normalize(tmp);
        } else {
            return secret;
        }
    }

    protected boolean verify(String alg, byte[] K_S, int index,
                             byte[] sig_of_H) throws Exception {
        int i, j;

        i = index;
        boolean result = false;

        if (alg.equals("ssh-rsa")) {
            byte[] tmp;
            byte[] ee;
            byte[] n;

            type = RSA;
            key_alg_name = alg;

            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            ee = tmp;
            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            n = tmp;

            SignatureRSA sig = null;
            try {
                Class c = Class.forName(session.getConfig("signature.rsa"));
                sig = (SignatureRSA) (c.newInstance());
                sig.init();
            } catch (Exception e) {
                System.err.println(e);
            }
            sig.setPubKey(ee, n);
            sig.update(H);
            result = sig.verify(sig_of_H);

            log.info("ssh_rsa_verify: signature " + result);

        } else if (alg.equals("ssh-dss")) {
            byte[] q = null;
            byte[] tmp;
            byte[] p;
            byte[] g;
            byte[] f;

            type = DSS;
            key_alg_name = alg;

            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            p = tmp;
            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            q = tmp;
            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            g = tmp;
            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            f = tmp;

            SignatureDSA sig = null;
            try {
                Class c = Class.forName(session.getConfig("signature.dss"));
                sig = (SignatureDSA) (c.newInstance());
                sig.init();
            } catch (Exception e) {
                System.err.println(e);
            }
            sig.setPubKey(f, p, q, g);
            sig.update(H);
            result = sig.verify(sig_of_H);

            if (JSch.getLogger().isEnabled(Logger.INFO)) {
                JSch.getLogger().log(Logger.INFO,
                        "ssh_dss_verify: signature " + result);
            }
        } else if (alg.equals("ecdsa-sha2-nistp256") ||
                alg.equals("ecdsa-sha2-nistp384") ||
                alg.equals("ecdsa-sha2-nistp521")) {
            byte[] tmp;
            byte[] r;
            byte[] s;

            // RFC 5656,
            type = ECDSA;
            key_alg_name = alg;

            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            tmp = new byte[j];
            System.arraycopy(K_S, i, tmp, 0, j);
            i += j;
            j = ((K_S[i++] << 24) & 0xff000000) | ((K_S[i++] << 16) & 0x00ff0000) |
                    ((K_S[i++] << 8) & 0x0000ff00) | ((K_S[i++]) & 0x000000ff);
            i++;
            tmp = new byte[(j - 1) / 2];
            System.arraycopy(K_S, i, tmp, 0, tmp.length);
            i += (j - 1) / 2;
            r = tmp;
            tmp = new byte[(j - 1) / 2];
            System.arraycopy(K_S, i, tmp, 0, tmp.length);
            i += (j - 1) / 2;
            s = tmp;

            SignatureECDSA sig = null;
            try {
                Class c = Class.forName(session.getConfig("signature.ecdsa"));
                sig = (SignatureECDSA) (c.newInstance());
                sig.init();
            } catch (Exception e) {
                System.err.println(e);
            }

            sig.setPubKey(r, s);

            sig.update(H);

            result = sig.verify(sig_of_H);
        } else {
            System.err.println("unknown alg");
        }

        return result;
    }

}
